﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Reflection.Emit;

namespace EJSX
{
	public static class JSDissembler
	{
		private static ILReader _ilReader;
		static JSDissembler()
		{
			_ilReader = new ILReader((a, b, c, d) => HandleInstruction(a, b, c, d));
		}

		public static string DissembleMethod(MethodInfo methodInfo, params object[] paramArray)
		{
			return _ilReader.DissembleMethod(methodInfo, paramArray);
		}

		public static string DissembleAction(object function)
		{
			return _ilReader.DissembleAction(function);
		}

		public static string DissembleClass(Type type)
		{
			StringBuilder sb = new StringBuilder();
			sb.Append("function ");
			sb.Append(type.Name);
			sb.AppendLine("() {");
			//foreach (PropertyInfo prop in type.GetProperties())
			//    sb.AppendFormat("this.{0} = null;\n", prop.Name);
			//foreach (PropertyInfo prop in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance))
			//    sb.AppendFormat("var {0} = null;\n", prop.Name);
			foreach (FieldInfo field in type.GetFields())
				sb.AppendFormat("this.{0} = {1};\n", ILReader.SanitizeIdentifier(field.Name), field.FieldType.IsValueType ? Activator.CreateInstance(field.FieldType).ToString() : "null");
			foreach (FieldInfo field in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
				sb.AppendFormat("var {0} = {1};\n", ILReader.SanitizeIdentifier(field.Name), field.FieldType.IsValueType ? Activator.CreateInstance(field.FieldType).ToString() : "null");
			foreach (MethodInfo method in type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
			{
				var paramStr = from i in Enumerable.Range(0, method.GetParameters().Length)
							select "arg" + i;
				sb.AppendFormat("this.{0} = function({1}) ", method.Name, string.Join(", ", paramStr.ToArray()));
				var paramObj = paramStr.Cast<object>();
				if (!method.IsStatic)
					paramObj = new object[] { "this" }.Concat(paramObj);
				sb.AppendFormat("{{\n{0} }}\n", DissembleMethod(method, paramObj.ToArray()));
			}
			sb.AppendLine("}");
			return sb.ToString();
		}

		private static string HandleInstruction(List<ILInstruction> instructions, ILInstruction inst, object[] paramArray, Stack stack)
		{
			if (inst.Code == OpCodes.Nop)
			{
			}
			else if (inst.Code == OpCodes.Ret)
			{
				if (stack.Count > 0)
					return "return " + stack.Pop();
			}
			else if (inst.Code == OpCodes.Ldarg_S)
			{
				int index = (int)(byte)inst.Operand;
				stack.Push(paramArray[index]);
			}
			else if (inst.Code.Name.StartsWith("ldarg."))
			{
				int index = _ilReader.GetEndingNumber(inst);
				stack.Push(paramArray[index].ToString());
			}
			else if (inst.Code == OpCodes.Stloc_S)
			{
				string var = _ilReader.GetLocalVariableName((int)(byte)inst.Operand);
				return var + " = " + (stack.Pop() ?? "null");
			}
			else if (inst.Code.Name.StartsWith("stloc."))
			{
				string var = _ilReader.GetLocalVariableName(_ilReader.GetEndingNumber(inst));
				return var + " = " + stack.Pop();
			}
			else if (inst.Code == OpCodes.Ldloc_S)
			{
				stack.Push(_ilReader.GetLocalVariableName((int)(byte)inst.Operand));
			}
			else if (inst.Code.Name.StartsWith("ldloc."))
			{
				stack.Push(_ilReader.GetLocalVariableName(_ilReader.GetEndingNumber(inst)));
			}
			else if (inst.Code == OpCodes.Ldc_I4)
			{
				stack.Push(inst.Operand);
			}
			else if (inst.Code == OpCodes.Ldstr)
			{
				stack.Push("'" + ((string)inst.Operand).Replace("\'", "\\\'") + "'");
			}
			else if (inst.Code == OpCodes.Ldloca_S)
			{
				stack.Push(_ilReader.GetLocalVariableName((int)(byte)inst.Operand));
			}
			else if (inst.Code == OpCodes.Add)
			{
				string arg2 = stack.Pop().ToString();
				string arg1 = stack.Pop().ToString();
				stack.Push(arg1 + " + " + arg2);
			}
			else if (inst.Code == OpCodes.Callvirt || inst.Code == OpCodes.Call)
			{
				MethodInfo method = (MethodInfo)inst.Operand;
				if (!method.IsStatic)
				{
					List<string> paramList = new List<string>();
					foreach (var i in Enumerable.Repeat(0, method.GetParameters().Length))
						paramList.Add(stack.Pop().ToString());
					string paramStr = string.Join(", ", paramList.ToArray());
					stack.Push(stack.Pop().ToString() + "." + method.Name + "(" + paramStr + ")");
				}
				else
				{
					List<string> paramList = new List<string>();
					foreach (var i in Enumerable.Repeat(0, method.GetParameters().Length))
						paramList.Add(stack.Pop().ToString());
					string paramStr = string.Join(", ", paramList.ToArray());
					stack.Push(method.DeclaringType.Name + "." + method.Name + "(" + paramStr + ")");
				}
				if (method.ReturnType == typeof(void))
					return stack.Pop().ToString();
			}
			//else if (inst.Code == OpCodes.Pop)
			//{
			//}
			else if (inst.Code == OpCodes.Newobj)
			{
				ConstructorInfo constructor = (ConstructorInfo)inst.Operand;
				string paramStr = string.Join(", ", _ilReader.GetParametersFromStack(stack, constructor).Cast<string>().ToArray());
				stack.Push(string.Format("new {0}({1})", constructor.DeclaringType.Name, paramStr));
			}
			else if (inst.Code == OpCodes.Stfld)
			{
				object value = stack.Pop();
				object called = stack.Pop();
				FieldInfo field = (FieldInfo)inst.Operand;
				return called.ToString() + "." + ILReader.SanitizeIdentifier(field.Name) + " = " + value;
			}
			//else if (inst.Code == OpCodes.Stsfld)
			//{
			//}
			//else if (inst.Code == OpCodes.Ldftn)
			//{
			//}
			else if (inst.Code == OpCodes.Ldfld)
			{
				FieldInfo fieldInfo = (FieldInfo)inst.Operand;
				stack.Push((string)stack.Pop() + "." + ILReader.SanitizeIdentifier(fieldInfo.Name));
			}
			else if (inst.Code == OpCodes.Ldsfld)
			{
				FieldInfo fieldInfo = (FieldInfo)inst.Operand;
				stack.Push(fieldInfo.GetValue(null));
			}
			else if (inst.Code == OpCodes.Ldc_I4_S)
			{
				stack.Push(Convert.ToInt32(inst.Operand));
			}
			else if (inst.Code.Name.StartsWith("ldc.i4."))
			{
				stack.Push(_ilReader.GetEndingNumber(inst));
			}
			//else if (inst.Code == OpCodes.Ldnull)
			//{
			//}
			else if (inst.Code == OpCodes.Dup)
			{
				stack.Push(stack.Peek());
			}
			else if (inst.Code == OpCodes.Box)
			{
				// javascript doesn't need boxing
			}
			else if (inst.Code == OpCodes.Castclass)
			{
				// javascript doesn't need to cast
			}
			// array methods
			else if (inst.Code == OpCodes.Newarr)
			{
				//stack.Push(Array.CreateInstance((Type)inst.Operand, (int)stack.Pop()));
				int size = (int)stack.Pop();
				stack.Push("[ ]");
			}
			else if (inst.Code == OpCodes.Stelem_Ref)
			{
				object value = stack.Pop();
				object index = stack.Pop();
				object array = stack.Pop();
				return string.Format("{0}[{1}] = {2}", array, index, value);
			}
			else if (inst.Code == OpCodes.Ldelem_Ref)
			{
				object index = stack.Pop();
				object array = stack.Pop();
				stack.Push(string.Format("{0}[{1}]", array, index));
			}
			else if (inst.Code == OpCodes.Ldelema)
			{
				object index = stack.Pop();
				object array = stack.Pop();
				stack.Push(string.Format("{0}[{1}]", array, index));
			}
			else if (inst.Code == OpCodes.Stobj)
			{
				object value = stack.Pop();
				object target = stack.Pop();
				return target + " = " + value;
			}
			else if (inst.Code == OpCodes.Ldobj)
			{
				stack.Push(stack.Pop());
			}
			else if (inst.Code == OpCodes.Ldlen)
			{
				stack.Push(stack.Pop() + ".length");
			}
			else if (inst.Code == OpCodes.Conv_I4)
			{
			}
			else if (inst.Code == OpCodes.Clt)
			{
				var a = stack.Pop();
				var b = stack.Pop();
				stack.Push(b.ToString() + " < " + a);
			}
			else if (inst.Code == OpCodes.Cgt || inst.Code == OpCodes.Cgt_Un)
			{
				var a = stack.Pop();
				var b = stack.Pop();
				stack.Push(b.ToString() + " > " + a);
			}
			else if (inst.Code == OpCodes.Ceq)
			{
				var a = stack.Pop();
				var b = stack.Pop();
				stack.Push(b.ToString() + " == " + a);
			}
			//else if (inst.Code == OpCodes.Ldc_R8)
			//{
			//}
			else if (inst.Code == OpCodes.Constrained)
			{
				// i don't know what this is
			}
			else if (inst.Code.FlowControl == FlowControl.Branch)
			{
				// assume it's a return with a value
			}
			else if (inst.Code.FlowControl == FlowControl.Cond_Branch)
				throw new NotImplementedException("branching instructions should be handled by the branch analyzer");
			else
				throw new NotImplementedException("The " + inst.Code.Name + " instruction was not handled: " + Environment.NewLine + MethodBodyReader.GetBodyCode(instructions));

			return null;
		}
	}
}
